package com.ls.shiro;

import com.github.pagehelper.util.StringUtil;
import com.ls.model.Permission;
import com.ls.model.Role;
import com.ls.model.User;
import com.ls.service.PermissionService;
import com.ls.service.RoleService;
import com.ls.service.UserService;
import com.ls.util.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p></p>
 * Created by zhezhiyong@163.com on 2017/2/17.
 */
public class UserRealm extends AuthorizingRealm {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Resource
    private UserService userService;
    @Resource
    private RoleService roleService;
    @Resource
    private PermissionService permissionService;

    /**
     * 登录验证
     *
     * @param token 是用来验证用户身份
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.debug("登录验证");
        //获取用户的输入的账号.
        String loginName = String.valueOf(token.getPrincipal());
        String password = new String((char[]) token.getCredentials());
        if (StringUtils.isEmpty(loginName)) {
            throw new AuthenticationException("用户名或密码错误.");
        }
        //通过username从数据库中查找 User对象，如果找到，没找到.
        //实际项目中，这里可以根据实际情况做缓存，如果不做，Shiro自己也是有时间间隔机制，2分钟内不会重复执行该方法
        User user = userService.findByLoginName(loginName);
        if (user == null) {
            throw new UnknownAccountException();//没找到帐号  ;
        }
//        this.setSession(KEY_SESSION, user);
        /*
         * 获取权限信息:这里没有进行实现，
		 * 请自行根据UserInfo,Role,Permission进行实现；
		 * 获取之后可以在前端for循环显示所有链接;
		 */
        //userInfo.setPermissions(userService.findPermissions(user));

        //账号判断;

        //加密方式;
        //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配，如果觉得人家的不好可以自定义实现
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user.getLoginName(), //用户名
                user.getPassword(), //密码
                new MySimpleByteSource(user.getCredentialsSalt().getBytes()),//salt=username+salt
                getName()  //realm name
        );

        //明文: 若存在，将此用户存放到登录认证info中，无需自己做密码对比，Shiro会为我们进行密码对比校验
//        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
//                user.getLoginName(), //用户名
//                user.getPassword(), //密码
//                getName()  //realm name
//        );

        return authenticationInfo;
    }

    /**
     * 此方法调用  hasRole,hasPermission的时候才会进行回调.
     * <p>
     * 权限信息.(授权):
     * 1、如果用户正常退出，缓存自动清空；
     * 2、如果用户非正常退出，缓存自动清空；
     * 3、如果我们修改了用户的权限，而用户不退出系统，修改的权限无法立即生效。
     * （需要手动编程进行实现；放在service进行调用）
     * 在权限修改后调用realm中的方法，realm已经由spring管理，所以从spring中获取realm实例，
     * 调用clearCached方法；
     * :Authorization 是授权访问控制，用于对用户进行的操作授权，证明该用户是否允许进行当前操作，如访问某个链接，某个资源文件等。
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        /*
         * 当没有使用缓存的时候，不断刷新页面的话，这个代码会不断执行，
		 * 当其实没有必要每次都重新设置权限信息，所以我们需要放到缓存中进行管理；
		 * 当放到缓存中时，这样的话，doGetAuthorizationInfo就只会执行一次了，
		 * 缓存过期之后会再次执行。
		 */
        logger.debug("shiro 授权");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        // 获取当前登录的用户名,等价于(String)principals.fromRealm(this.getName()).iterator().next()
        // String currentUsername =
        // (String)super.getAvailablePrincipal(principals);
        String loginName = String.valueOf(principals.getPrimaryPrincipal());

        if (StringUtil.isEmpty(loginName)) {
            throw new AuthorizationException();
        }

        final User user = userService.findByLoginName(loginName);
        final Role role = roleService.findByUserId(user.getId());
        List<Permission> permissionList = permissionService.findByRoleId(role.getId());
        authorizationInfo.addRole(role.getRole());
        List<String> pList = permissionList.stream().map(Permission::getPermission).collect(Collectors.toList());
        authorizationInfo.addStringPermissions(pList);
        return authorizationInfo;
    }

    /**
     * 将一些数据放到ShiroSession中,以便于其它地方使用
     * <p>
     * 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setSession(Object key, Object value) {
        Subject currentUser = SecurityUtils.getSubject();
        if (null != currentUser) {
            Session session = currentUser.getSession();
            logger.debug("Session默认超时时间为[" + session.getTimeout() + "]毫秒");
            session.setAttribute(key, value);
        }
    }
}
